<!--
 * @Description:  具体使用方式请参考同文件夹下 VueComponent 和 VuePlugIn 文件           
 * @Version: 1.0
 * @Autor: chen_yt
 * @Date: 2020-11-02 17:15:31
 * @LastEditors: chen_yt
 * @LastEditTime: 2022-07-19 10:23:33
-->
<template>
  <div />
</template>
<script>
import mqtt from 'mqtt'
import store from '@/store'
import storage from '@/utils/storage'
import { transRelativeToAbsolute } from './util'

export default {
  name: 'SmqttConnect',
  props: {
    //是否链接凤翎服务，是的话会在url后拼接一些参数
    isFksServer: {
      type: Boolean,
      default: true
    },
    name: {
      type: String,
      required: true
    },
    //链接地址。支持以相对地址构建，比如../api/server/xxx
    url: {
      type: String,
      default: './api/sys-socket-smqtt/mqtt'
    },
    //后端控制器的messagingTemplate.convertAndSendToUser中可设置推送地址地址，该地址与前端消息模板中的订阅地址一致
    //e：消息模板中订阅地址为'/topic/message',后端将消息推送到'/topic/message',前端订阅'/topic/message'
    destination: {
      type: String
    },
    //链接建立方式，现在仅支持stomp及sqmtt
    connectType: {
      type: String,
      default: 'stomp'
    },
    //尝试链接等待5秒，连不上就断开
    connectPendingTime: {
      type: Number,
      default: 5000
    },
    //链接断开后的重连间隔
    reConnectInterval: {
      type: Number,
      default: 1000
    },
    maxReconnect: {
      type: Number,
      default: 15
    },
    clientInPool: {
      type: Object
    }
  },
  data() {
    return {
      //定时器,一段时间连不上就断开
      pendingTimer: null,
      //重连轮次
      connectRound: -1,
      smqttClient: {},
      smqttOptions: {
        keepalive: 60,
        clientId: '',
        username: '',
        password: '',

        protocolId: 'MQTT',
        protocolVersion: 4,
        clean: true,
        reconnectPeriod: 10 * 1000,
        connectTimeout: 300 * 1000,
        will: {
          topic: 'WillMsg',
          payload: 'Connection Closed abnormally..!',
          qos: 0,
          retain: false
        }
      }
    }
  },
  computed: {
    currentClient() {
      return this.smqttClient
    },
    CLIENT() {
      return store.getters.clientConfig.clientId
    },
    sendMessage() {
      return this.currentClient.send
    },
    user() {
      return store.state.user
    }
  },
  mounted() {
    this.initConnect()
    window.addEventListener('visibilitychange', this.onVisibilityChange)
  },
  beforeDestroy() {
    this.$emit('closeClient')
    window.removeEventListener('visibilitychange', this.onVisibilityChange)
  },
  methods: {
    /**
     * @description:建立链接的入口，当未给链接命名时拒绝链接
     *
     */
    initConnect() {
      this.smqttClient = {}

      this.connectRound = -1

      this.connection()
    },
    /**
     * @description:建立基于smqtt协议的链接，创建的smqtt对象会保存到vuex的链接池中
     */
    connection() {
      let options = { ...this.smqttOptions }

      //如果是凤翎提供的ws服务，加入一些识别参数
      if (this.isFksServer) {
        let accessToken = storage.get('access_token')
        if (!accessToken) {
          this.emitErrorMsg('缺少token')
          this.connectLock = false
          return
        }
        options.password = accessToken
        options.clientId = `${this.CLIENT}_web_${
          this.name
        }${Math.random().toString(16).substr(2, 8)}`
        options.username = this.user.userName

        options.reconnectPeriod = this.reConnectInterval
      }

      this.connectRound++

      console.log(
        `try connect round ${this.connectRound + 1}：${this.connectType}-${
          this.name
        }`
      )

      // STOMP子协议的客户端对象
      this.smqttClient = {}

      let connectionUrl = this.url
      //使用原生websocket构建smqtt时，要将相对路径转为绝对路径
      connectionUrl = transRelativeToAbsolute(connectionUrl)

      this.smqttClient = mqtt.connect(connectionUrl, options)

      //设置smqtt基础信息并存入连接池
      this.$set(this.smqttClient, 'name', this.name)
      this.$set(this.smqttClient, 'activeClose', false)
      this.$set(this.smqttClient, 'activeEnd', this.activeCloseMethod)

      if (this.connectRound == 0) {
        this.$emit('createClient')
      } else {
        this.$emit('updateClient')
      }

      // 向服务器发起websocket连接
      this.smqttClient.on('connect', this.onSuccessHandle)
      this.smqttClient.on('error', this.onErrorHandle)
      this.smqttClient.on('reconnect', this.onReconnectHandle)
      this.smqttClient.on('message', this.onNewMessageHandle)
      this.smqttClient.on('disconnect', this.onCloseHandle)
    },

    reconnect() {
      if (this.currentClient.connected) return
      if (this.connectRound < this.maxReconnect) return

      this.connectRound = 0

      this.smqttClient.end(this.connection())
    },

    /**
     * @description: 主动关闭连接
     */
    activeCloseMethod() {
      this.$emit('onClose')
      this.smqttClient.activeClose = true
      this.disconnect()
    },

    /**
     * @description: 断开链接
     */
    disconnect() {
      this.smqttClient.end()
    },

    onNewMessageHandle(topic, payload, packet) {
      // message is Buffer
      packet.body = packet.payload ? packet.payload.toString() : ''
      this.$emit('onNewMessage', packet)
    },

    onSuccessHandle() {
      if (this.destination) {
        this.smqttClient.subscribe(
          `${this.destination}/${this.CLIENT}/${this.user.userName}`,
          function (err) {}
        )

        this.smqttClient.subscribe(
          `${this.destination}/${this.CLIENT}`,
          function (err) {}
        )
      }
      this.$emit('updateClient')

      this.$emit('onSuccess')
    },

    onErrorHandle(e) {
      this.$emit('onError', e)

      this.$emit('updateClient')
    },

    onCloseHandle() {
      this.$emit('onClose')
    },

    onReconnectHandle(e) {
      console.log(
        `try connect round ${this.connectRound}：${this.connectType}-${this.name}`
      )

      this.connectRound++

      if (this.connectRound >= this.maxReconnect) {
        this.emitErrorMsg('重连达到最大次数')
        this.activeCloseMethod()
      }
    },

    emitErrorMsg(e) {
      this.$emit('onError', e)
    },

    /**
     * @description: 监听标签页是否在前台显示，防止链接处在后台过久断连
     */
    onVisibilityChange() {
      this.visibilityState = document.visibilityState

      if (document.visibilityState != 'hidden') {
        this.reconnect()
      }
    }
  }
}
</script>

